#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
#include "matrix.hpp"
#include "vector.hpp"

#include <iostream>
#include <list>
#include <cmath>
#include <regex>
#include <complex>
#include <appendix/point.h>
#include <parsers/parsers.hpp>

namespace sol {
   template <>
   struct is_automagical<Point3Dd> : std::false_type {};
   template <>
   struct is_automagical<Point2Dd> : std::false_type {};
   template <>
   struct is_automagical<Point3Di> : std::false_type {};
   template <>
   struct is_automagical<Point2Di> : std::false_type {};
}

template<class T>
sol::object getter(const std::vector<T>& vec, sol::stack_object key, sol::this_state L) {
		// we use stack_object for the arguments because we know
		// the values from Lua will remain on Lua's stack,
		// so long we we don't mess with it
		// String keys failed, check for numbers
		auto maybe_numeric_key = key.as<sol::optional<int>>();
		if (maybe_numeric_key) {
			int n = *maybe_numeric_key;
			if( n <= vec.size() && n >= 1)
				return sol::object(L, sol::in_place, vec[n-1]);
		}
		// No valid key: push nil
		// Note that the example above is a bit unnecessary:
		// binding the variables x and y to the usertype
		// would work just as well and we would not
		// need to look it up here,
		// but this is to show it works regardless
		return sol::object(L, sol::in_place, sol::lua_nil);
}
template<class T>
void setter(std::vector<T>& vec, int idx, const T& val) {
	if( idx <= vec.size() && idx >= 1)
		vec[idx-1] = val;
	else
		throw sol::error("out of range");
}


namespace LuaNumeric {

template<class T>
sol::object getter(const T& vec, sol::stack_object key, sol::this_state L) {
		// we use stack_object for the arguments because we know
		// the values from Lua will remain on Lua's stack,
		// so long we we don't mess with it
		// String keys failed, check for numbers
		auto maybe_numeric_key = key.as<sol::optional<int>>();
		if (maybe_numeric_key) {
			int n = *maybe_numeric_key;
			if( n <= vec.size() && n >= 1)
				return sol::object(L, sol::in_place, vec[n-1]);
		}
		// No valid key: push nil
		// Note that the example above is a bit unnecessary:
		// binding the variables x and y to the usertype
		// would work just as well and we would not
		// need to look it up here,
		// but this is to show it works regardless
		return sol::object(L, sol::in_place, sol::lua_nil);
}
template<class T,class TV>
void setter(T& vec, int idx, const TV& val) {
	if( idx <= vec.size() && idx >= 1)
		vec[idx-1] = val;
	else
		throw sol::error("out of range");
}


	void open_luaPoint(sol::table& module) {

		module.set_function("new_point3Dd", sol::overload(
											[]() { return std::make_shared<Point3Dd>(); }
											,[](double x, double y , double z) { return std::make_shared<Point3Dd>(x,y,z); }
							)
		);

		module.new_usertype<Point3Dd>("Point3Dd"
					,sol::no_constructor
					,"x", sol::overload(
							( const double& (Point3Dd::*)(void) const) & Point3Dd::x
							,( Point3Dd& (Point3Dd::*)(const double& ) ) & Point3Dd::x
							)
					,"y", sol::overload(
							( const double& (Point3Dd::*)(void) const) & Point3Dd::y
							,( Point3Dd& (Point3Dd::*)(const double& ) ) & Point3Dd::y
							)
					,"z", sol::overload(
							( const double& (Point3Dd::*)(void) const) & Point3Dd::z
							,( Point3Dd& (Point3Dd::*)(const double& ) ) & Point3Dd::z
					)
					,"rho", & Point3Dd::rho
					,"abs", & Point3Dd::abs
					,"phi", & Point3Dd::phi
					,"theta", & Point3Dd::theta
					//,sol::meta_function::index, &getter<Point3Dd>
					,sol::meta_function::index, [](const Point3Dd& pt,int n, sol::this_state L){
//							return pt[n-1];
							if( n <= pt.size() && n >= 1)
									return sol::object(L, sol::in_place, pt[n-1]);
							return sol::object(L, sol::in_place, sol::lua_nil);
					}
					,sol::meta_function::new_index, &setter<Point3Dd,double>
					//,sol::meta_function::index, &getter<Point3Dd>
					//,sol::meta_function::new_index, &setter<Point3Dd>
			);



		module.new_usertype<Point3Di>("Point3Di"
					,sol::no_constructor
					,"x", sol::overload(
							( const int& (Point3Di::*)(void) const) & Point3Di::x
							,( Point3Di& (Point3Di::*)(const int& ) ) & Point3Di::x
					)
					,"y", sol::overload(
							( const int& (Point3Di::*)(void) const) & Point3Di::y
							,( Point3Di& (Point3Di::*)(const int& ) ) & Point3Di::y
					)
					,"z", sol::overload(
							( const int& (Point3Di::*)(void) const) & Point3Di::z
							,( Point3Di& (Point3Di::*)(const int& ) ) & Point3Di::z
					)
					,sol::meta_function::index, &getter<Point3Di>
					,sol::meta_function::new_index, &setter<Point3Di,int>
			);

		module.new_usertype<Point2Di>("Point2Di"
					,sol::no_constructor
					,"x", sol::overload(
							( const int& (Point2Di::*)(void) const) & Point2Di::x
							,( Point2Di& (Point2Di::*)(const int& ) ) & Point2Di::x
					)
					,"y", sol::overload(
							( const int& (Point2Di::*)(void) const) & Point2Di::y
							,( Point2Di& (Point2Di::*)(const int& ) ) & Point2Di::y
					)
					,sol::meta_function::index, &getter<Point2Di>
					,sol::meta_function::new_index, &setter<Point2Di,int>

			);

		module.new_usertype<Point2Dd>("Point2Dd"
					,sol::no_constructor
					,"x", sol::overload(
							( const double& (Point2Dd::*)(void) const) & Point2Dd::x
							,( Point2Dd& (Point2Dd::*)(const double& ) ) & Point2Dd::x
					)
					,"y", sol::overload(
							( const double& (Point2Dd::*)(void) const) & Point2Dd::y
							,( Point2Dd& (Point2Dd::*)(const double& ) ) & Point2Dd::y
					)
					,sol::meta_function::index, &getter<Point2Dd>
					,sol::meta_function::new_index, &setter<Point2Dd,double>
			);

	}



} // namespace my_object

// automatically bound to tostring(obj) [ __tostring ]

